Skip to content

feat(types): enforce typecheck for frontend#144

Merged
almogdepaz merged 17 commits into
mainfrom
feat-typecheck-frontend
May 17, 2026
Merged

feat(types): enforce typecheck for frontend#144
almogdepaz merged 17 commits into
mainfrom
feat-typecheck-frontend

Conversation

@almogdepaz
Copy link
Copy Markdown
Owner

summary

Enables typechecking for the frontend and fixes all resulting errors under a relaxed public/ config.

included

  • extract diagnostic tracer from \ into \
  • annotate existing empty \ blocks with rationale
  • add ambient browser globals for , , , and ghostty handoff globals
  • add \ with relaxed frontend flags:
    • \
    • \
  • fix all root tsc errors, including the \ interface drift
  • add \
  • add CI typecheck step for root + public/
  • regenerate embedded assets

verification

�[1m Dependencies�[0m
�[32m✓�[0m tailscale�[2m — 1.94.2�[0m
�[32m✓�[0m tailscale connected�[2m — sgt.tail03f8e8.ts.net�[0m
�[32m✓�[0m shell�[2m — /bin/zsh�[0m

�[1m Config�[0m
�[32m✓�[0m ~/.wolfpack/�[2m — exists�[0m
�[32m✓�[0m config.json�[2m — port=18790, devDir=/Users/home/Dev�[0m
�[32m✓�[0m devDir�[2m — /Users/home/Dev�[0m

�[1m Service�[0m
�[32m✓�[0m service installed�[2m — launchd�[0m
�[32m✓�[0m service running�[2m — active�[0m
�[32m✓�[0m port 18790�[2m — in use, service active�[0m

�[1m Connectivity�[0m
�[32m✓�[0m localhost�[2m — v1.6.3�[0m
�[32m✓�[0m tailscale hostname�[2m — sgt.tail03f8e8.ts.net�[0m

�[1m Binary�[0m
�[32m✓�[0m binary�[2m — 58.4MB�[0m
�[32m✓�[0m binary executable�[2m — yes�[0m
�[32m✓�[0m codesign�[2m — valid�[0m

�[1m Broker�[0m
�[32m✓�[0m broker binary�[2m — /Users/home/.wolfpack/bin/wolfpack-broker�[0m
�[32m✓�[0m broker handshake�[2m — list_sessions ok (6ms)�[0m

�[1m Environment�[0m
�[32m✓�[0m PATH includes /opt/homebrew/bin�[2m — yes�[0m
�[32m✓�[0m PATH includes /usr/local/bin�[2m — yes�[0m
�[32m✓�[0m HOME�[2m — /Users/home�[0m

�[1m Logs�[0m
�[32m✓�[0m wolfpack.log�[2m — 436KB, modified 1m ago�[0m
�[33m⚠�[0m recent errors�[2m — 64 error(s) in last 100 lines�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m

�[32m Result: 20/24 passed�[0m�[33m, 4 warning(s)�[0m

�[1m Dependencies�[0m
�[32m✓�[0m tailscale�[2m — 1.94.2�[0m
�[32m✓�[0m tailscale connected�[2m — sgt.tail03f8e8.ts.net�[0m
�[32m✓�[0m shell�[2m — /bin/zsh�[0m

�[1m Config�[0m
�[32m✓�[0m ~/.wolfpack/�[2m — exists�[0m
�[32m✓�[0m config.json�[2m — port=18790, devDir=/Users/home/Dev�[0m
�[32m✓�[0m devDir�[2m — /Users/home/Dev�[0m

�[1m Service�[0m
�[32m✓�[0m service installed�[2m — launchd�[0m
�[32m✓�[0m service running�[2m — active�[0m
�[32m✓�[0m port 18790�[2m — in use, service active�[0m

�[1m Connectivity�[0m
�[32m✓�[0m localhost�[2m — v1.6.3�[0m
�[32m✓�[0m tailscale hostname�[2m — sgt.tail03f8e8.ts.net�[0m

�[1m Binary�[0m
�[32m✓�[0m binary�[2m — 58.4MB�[0m
�[32m✓�[0m binary executable�[2m — yes�[0m
�[32m✓�[0m codesign�[2m — valid�[0m

�[1m Broker�[0m
�[32m✓�[0m broker binary�[2m — /Users/home/.wolfpack/bin/wolfpack-broker�[0m
�[32m✓�[0m broker handshake�[2m — list_sessions ok (2ms)�[0m

�[1m Environment�[0m
�[32m✓�[0m PATH includes /opt/homebrew/bin�[2m — yes�[0m
�[32m✓�[0m PATH includes /usr/local/bin�[2m — yes�[0m
�[32m✓�[0m HOME�[2m — /Users/home�[0m

�[1m Logs�[0m
�[32m✓�[0m wolfpack.log�[2m — 436KB, modified 1m ago�[0m
�[33m⚠�[0m recent errors�[2m — 64 error(s) in last 100 lines�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m

�[32m Result: 20/24 passed�[0m�[33m, 4 warning(s)�[0m

�[1m Dependencies�[0m
�[32m✓�[0m tailscale�[2m — 1.94.2�[0m
�[32m✓�[0m tailscale connected�[2m — sgt.tail03f8e8.ts.net�[0m
�[32m✓�[0m shell�[2m — /bin/zsh�[0m

�[1m Config�[0m
�[32m✓�[0m ~/.wolfpack/�[2m — exists�[0m
�[32m✓�[0m config.json�[2m — port=18790, devDir=/Users/home/Dev�[0m
�[32m✓�[0m devDir�[2m — /Users/home/Dev�[0m

�[1m Service�[0m
�[32m✓�[0m service installed�[2m — launchd�[0m
�[32m✓�[0m service running�[2m — active�[0m
�[32m✓�[0m port 18790�[2m — in use, service active�[0m

�[1m Connectivity�[0m
�[32m✓�[0m localhost�[2m — v1.6.3�[0m
�[32m✓�[0m tailscale hostname�[2m — sgt.tail03f8e8.ts.net�[0m

�[1m Binary�[0m
�[32m✓�[0m binary�[2m — 58.4MB�[0m
�[32m✓�[0m binary executable�[2m — yes�[0m
�[32m✓�[0m codesign�[2m — valid�[0m

�[1m Broker�[0m
�[32m✓�[0m broker binary�[2m — /Users/home/.wolfpack/bin/wolfpack-broker�[0m
�[32m✓�[0m broker handshake�[2m — list_sessions ok (2ms)�[0m

�[1m Environment�[0m
�[32m✓�[0m PATH includes /opt/homebrew/bin�[2m — yes�[0m
�[32m✓�[0m PATH includes /usr/local/bin�[2m — yes�[0m
�[32m✓�[0m HOME�[2m — /Users/home�[0m

�[1m Logs�[0m
�[32m✓�[0m wolfpack.log�[2m — 436KB, modified 1m ago�[0m
�[33m⚠�[0m recent errors�[2m — 64 error(s) in last 100 lines�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m

�[32m Result: 20/24 passed�[0m�[33m, 4 warning(s)�[0m

�[1m Dependencies�[0m
�[32m✓�[0m tailscale�[2m — 1.94.2�[0m
�[32m✓�[0m tailscale connected�[2m — sgt.tail03f8e8.ts.net�[0m
�[32m✓�[0m shell�[2m — /bin/zsh�[0m

�[1m Config�[0m
�[32m✓�[0m ~/.wolfpack/�[2m — exists�[0m
�[32m✓�[0m config.json�[2m — port=18790, devDir=/Users/home/Dev�[0m
�[32m✓�[0m devDir�[2m — /Users/home/Dev�[0m

�[1m Service�[0m
�[32m✓�[0m service installed�[2m — launchd�[0m
�[32m✓�[0m service running�[2m — active�[0m
�[32m✓�[0m port 18790�[2m — in use, service active�[0m

�[1m Connectivity�[0m
�[32m✓�[0m localhost�[2m — v1.6.3�[0m
�[32m✓�[0m tailscale hostname�[2m — sgt.tail03f8e8.ts.net�[0m

�[1m Binary�[0m
�[32m✓�[0m binary�[2m — 58.4MB�[0m
�[32m✓�[0m binary executable�[2m — yes�[0m
�[32m✓�[0m codesign�[2m — valid�[0m

�[1m Broker�[0m
�[32m✓�[0m broker binary�[2m — /Users/home/.wolfpack/bin/wolfpack-broker�[0m
�[32m✓�[0m broker handshake�[2m — list_sessions ok (1ms)�[0m

�[1m Environment�[0m
�[32m✓�[0m PATH includes /opt/homebrew/bin�[2m — yes�[0m
�[32m✓�[0m PATH includes /usr/local/bin�[2m — yes�[0m
�[32m✓�[0m HOME�[2m — /Users/home�[0m

�[1m Logs�[0m
�[32m✓�[0m wolfpack.log�[2m — 436KB, modified 1m ago�[0m
�[33m⚠�[0m recent errors�[2m — 64 error(s) in last 100 lines�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m
�[2m{"error":"BrokerRequestTimeoutError: broker request 'list_sessions' timed out after 10000ms","ts":"2�[0m

�[32m Result: 20/24 passed�[0m�[33m, 4 warning(s)�[0m — 1162 pass

  • \Bundled 6 modules in 23ms

    wp-test-build.js 203.74 KB (entry point) — pass

  • \bundling ghostty-web...
    bundled ghostty-web → /Users/home/Dev/wolfpack/public/ghostty-web.bundle.js (625.9 KB)
    bundling wolfpack-lib...
    bundled wolfpack-lib → /Users/home/Dev/wolfpack/public/wolfpack-lib.js (5.6 KB)
    bundling app...
    bundled app → /Users/home/Dev/wolfpack/public/app.bundle.js (199.0 KB)
    generated /Users/home/Dev/wolfpack/src/public-assets.ts (17 files embedded) — pass, committed \

notes

public/ is typechecked with relaxed flags for now. follow-up branch should tighten:

  1. enable \ for public/
  2. enable \ for public/
  3. remove public/ overrides once strict-clean

Untracked antipattern report/catalog files were intentionally left out of this PR.

almogdepaz added 17 commits May 15, 2026 12:33
each empty `catch {}` in public/app.ts now has a \u22655 word inline
comment explaining why swallowing the error is correct (teardown race,
quota limit, private-mode storage, best-effort discovery, etc.).

no behavior change. addresses antipattern-scan finding #5
(error hiding) on PR #143's followup branch.
types primitive-only function signatures in public/app.ts: quick-cmd
ops (4), session/recents helpers (2), error/url validation (3),
machine list mutators (3), small UI fns (8).

NOTE: public/ is not yet included in tsconfig's typecheck \u2014 these
annotations are documentation today, but become enforced when frontend
typechecking is enabled. behavior unchanged; bundle still parses
clean (`bun build public/app.ts`).

addresses subset 6a of antipattern-scan finding #6 (untyped function
parameters). 26 remaining will be split across follow-up commits
(tracer helpers, controller factory opts, renderer/data shapes).
moves the `__wfTrace*` family + `__wf_lastCrash` capture out of app.ts
into a self-contained, fully-typed module. `window` mutations are now
declared via `declare global { interface Window { ... } }` instead of
`(window as any)` casts.

  app.ts:        4470 \u2192 4352 LOC (-118)
  app-debug.ts:  new, 209 LOC, full type annotations

resolves antipattern-scan finding #4 (action at a distance via window
mutations) AND partial #6b (6 tracer fns now typed). bundle still parses
clean; unit tests green (1150 pass).

the localStorage gate (`wolfpackDebug = "1"`) is preserved \u2014 helpers
no-op when disabled, no `window.__wf*` globals installed. follow-up
could explore build-time exclusion via bundler defines, but not needed
for the antipattern fix.
the `if (connState === "displaced")` branch at setConnState was
unreachable \u2014 b85a00e (Mar 2026) deleted the `takeBackControl` helper
the branch referenced, AND rerouted the displaced disconnect path
through `showDesktopConflictOverlay()` instead. the only caller that
WOULD have triggered this branch (`onDisconnected` w/ classify=='displaced')
now calls `showDesktopConflictOverlay()` directly (line 2728).

confirmed: setConnState is only called with 'live', 'reconnecting',
'offline', 'session-ended' \u2014 never 'displaced'. takeBackControl
would have been a runtime ReferenceError if reached.

cleanup, not behavior change. removes 9 LOC of dead code and one of
the 24 TS2304 errors blocking public/ typecheck.
declares the globals that the frontend bundle relies on but cannot
import directly because they're installed via script tags:

  - WP            \u2192 typed via src/wolfpack-client-lib.ts (source of truth)
  - Terminal      \u2192 ghostty-web (`any` for now; refine in follow-up)
  - FitAddon      \u2192 ghostty-web (`any` for now; refine in follow-up)
  - window.ghosttyReady, .wasmFailed, .createIsolatedGhostty

removes the need for ad-hoc `(window as any).X` casts at every callsite
and kills 24 TS2304 errors that would surface when public/ is added to
tsconfig include (planned in follow-up commits).

groundwork for relaxed-strict frontend typecheck (Option B in
plans/PLAN-typecheck-frontend.md \u2014 plan file is gitignored).
four function signatures had params marked required even though all
callers omitted them and the bodies handled `undefined` correctly. mark
them optional with `?` so tsc accepts the actual calling convention:

  api(path, opts?, machineUrl?)
  showView(name, skipAnimation?)
  initTerminal(cached?)
  showProjectPicker(machineUrl?)
  closeDrawer(instant?)  (was typed required in earlier commit)

also: backBtn.onclick() in app.ts:3939 calls the handler with 0 args
programmatically. DOM `onclick` signature expects MouseEvent \u2014 pass
a synthetic one. Handlers ignore the event arg in practice.

resolves 33 TS2554 errors (87 remaining; mostly TS2339 "property doesn't
exist" which is B3). bundle clean, 1150 tests pass.
`document.getElementById()` returns the generic HTMLElement type, which
doesn't have `.value`, `.placeholder`, `.type`, `.checked` properties.
cast at each declaration to the actual element subtype:

  - msg-input (textarea)             \u2192 HTMLTextAreaElement
  - mobile-kb-proxy, *-name-input,
    agent-add-input, setting-* (input) \u2192 HTMLInputElement

also declares state.currentRalphCleanup/AuditFix in app-state.ts so
app-ralph.ts can read+write them with proper types instead of relying
on dynamic property assignment.

resolves 32 TS2339 errors (`Property X does not exist on HTMLElement`
+ 8 `Property does not exist on state`). bundle clean, 1150 tests pass.

remaining public/ errors: 41 (mostly EventTarget/Element narrowing,
opts:\{\} on createReconnector, and a few real type mismatches).
  - typed createReconnector opts (interface ReconnectorOpts) \u2014 4 props
  - fixed triageUi() return type: was string, is { dot, card, label, title }
  - cast textContent number assignments to String() (5 sites in debug panel)
  - narrowed errorMessage(err: unknown) with proper in-check
  - typed api() local data as `any` (response shape varies per endpoint)
  - augmented Error w/ .status + .data fields for /api/ rejections
  - cast Element \u2192 HTMLElement at .dataset/.classList/.onclick callsites
  - cast e.target \u2192 HTMLElement | Element for EventTarget narrowing
  - cast unknown \u2192 boolean/string in initSettings DOM write boundary
  - cast urlBase64ToUint8Array result \u2192 BufferSource for PushManager
  - synthetic event in backBtn.onclick() switched MouseEvent \u2192 PointerEvent

result: ALL public/ tsc errors resolved against the relaxed config.
remaining 12 errors are pre-existing in src/ and tests/ \u2014 not from
this branch. bundle clean, 1150 unit tests pass.
`BrokerClient.unsubscribe()` returns `Promise<void>` but the
`BrokerClientApi` interface in broker-backend.ts declared
`Promise<ControlResponse | null>`. unify to the impl's contract:

  - update interface return type to `Promise<void>`
  - update FakeBrokerClient in tests/unit/broker-backend.test.ts to match

confirmed safe: the only caller (broker-backend.ts:401) does
`.unsubscribe(id).catch(...)` and never reads the response.

also: fix TS7019 in tests/e2e/grid.e2e.ts \u2014 rest param `args` typed
explicitly as `unknown[]` in monkey-patched reconnect callback.

resolves 8 pre-existing tsc errors. `tsc -p .` now zero errors;
1150 tests pass.
separate tsconfig for public/*.ts so the frontend can be typechecked
independently. uses relaxed flags (`noImplicitAny: false`,
`strictNullChecks: false`) for now \u2014 strict pass is deferred (see
PLAN-typecheck-frontend.md \u2192 S1/S2 in a future branch).

with this:
  bunx tsc --noEmit -p .         \u2192 0 errors (strict)
  bunx tsc --noEmit -p public/   \u2192 0 errors (relaxed)

devs running typecheck locally now have a meaningful frontend gate
even before CI picks it up (added in follow-up commit).
adds:
  - new CI step "Typecheck (root + public/)" running
    `tsc --noEmit -p .` and `tsc --noEmit -p public/` sequentially,
    placed before `bun test` so type errors surface fast
  - `bun run typecheck` npm script for local invocation (matches CI)

both must pass for the workflow to succeed. catches:
  - any strict tsc violation in src/, tests/, scripts/ (strict config)
  - any relaxed tsc violation in public/ (no implicit any check or
    null-check enforcement, but everything else: TS2339 missing props,
    TS2554 wrong args, TS2322 mismatches \u2014 all enforced)

local devs run `bun run typecheck` to validate the same gate.
…setConnState

- errorMessage: replace triple-cast chain with single Record<string, unknown>
  intermediate — cleaner narrowing, same behavior
- setConnState: remove meaningless literal union with string (string
  subsumes the literals; they were documentation noise)
… any, add structural types

- add typescript@^6.0.3 as pinned devDependency
- ci typecheck uses bun run typecheck (not duplicated bunx commands)
- replace ghostty Terminal/FitAddon any globals with structural interfaces
- make diagnostic types readonly where mutation isn't needed
- api() is now generic with unknown internals instead of any
- add return type annotations on touched module-level functions
- add PtySocketClient, Reconnector, PtyTerminalController interfaces
- replace (window as any) with properly declared window globals
- replace this: any event handlers with this: HTMLInputElement
- document public/tsconfig.json strictness gap as intentional migration debt
ghostty-web's dirty-cell tracking may think it already painted while the
canvas was hidden (opacity:0 during hydration). When hydration.finish()
reveals the terminal, it now calls forceRepaint() via onReveal callback
to force a full canvas repaint, fixing the stale/blank grid cell issue
that previously required a manual resize to recover.
…InitialHydrationController, fix formatSnapshotTtl coercion
@almogdepaz almogdepaz merged commit c583109 into main May 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant